Skip to content

docs(website): re-shoot the docs stills on the 0.69 UI, and make the rig reproducible - #828

Closed
edspencer wants to merge 7 commits into
mainfrom
docs/media-v069
Closed

docs(website): re-shoot the docs stills on the 0.69 UI, and make the rig reproducible#828
edspencer wants to merge 7 commits into
mainfrom
docs/media-v069

Conversation

@edspencer

@edspencer edspencer commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Why

Every UI-bearing asset on the site was shot before the design work landed. The
seven stills merged in the previous pass predate the first design commit (67e493f)
by 41 minutes, so they are pre-redesign exactly like the older ones. Age was never the
criterion — three of these were a day old and still wrong, because of what landed
after they were committed.

These sit under prose in the present tense, on pages a reader consults to operate
the product. A pre-redesign frame there is worse than no image: a reader who cannot
find the pictured control concludes the docs are stale everywhere.

Re-shot — 7 stills

getting-started/root-home.png · config/project-settings.png ·
using/adopt-row.png · using/adopt-modal.png · using/adopted-badge.png ·
using/promote-to-project.png · guides/trigger-tool-picker-bash.png

All at v0.69.0 in Foundation / dark / hue: null / tint: 0 — the out-of-the-box
appearance (DEFAULT_APPEARANCE; paddock:theme defaults to dark). That is what a
reader sees on first boot, which is a docs screenshot's whole job.

Two alt-text corrections ride along, and they are not cosmetic — alt text is the
only description a screen-reader user gets. getting-started.md claimed four unread
chats and named OVERVIEW.MD/CHANGELOG.MD sections; the frame shows nine unread, three
projects grouped by area, and RUNNING/UNREAD/FILES.

Not here: the theme quartet and the Appearance panel

Those five stills live in #815, which also carries the 0.67 prose that references
them. Keeping a copy here would have made them orphans on this branch and put the same
bytes in two PRs. This branch has zero orphans and zero dangling references
checked mechanically, both directions.

Making the rig reproducible

seed.mjs and serve.sh existed only on one machine and were full of its paths and
ports — so capture.mjs, which is committed, was unusable by anyone else. Both are
now committed, driven by four env vars (PADDOCK_RIG_HOME / _CLONE / _PROJECTS /
_BASE), with no box paths.

Worth reviewing rather than skimming:

  • serve.sh was retyped from a spec, not copied. The original held a live OAuth
    token in plaintext; those lines are gone rather than edited. A rig running a fake
    claude must never need a credential.
  • The projects root moved off /home. The previous rig kept it there on a box
    where only /data was a volume — a container restart destroyed every project.yaml
    and every .chats/*.jsonl while the data dir survived, leaving orphaned job records
    reporting chats whose transcripts were gone. Default is now $PADDOCK_RIG_HOME/projects.
  • It binds loopback, not 0.0.0.0. With PADDOCK_AUTH_MODE=none, binding all
    interfaces publishes an unauthenticated instance. Capture runs on the same host, so
    PADDOCK_DANGEROUSLY_ALLOW_OPEN is no longer needed at all.
  • capture.mjs pins the theme via addInitScript and asserts it applied. It has
    to be addInitScript: the three paddock:* keys are read by a pre-paint inline
    script, so a page.evaluate after goto catches a mid-swap frame — and a stale
    paddock:appearance-cache paints the previous theme's accent for one frame.
  • shots.manifest.json records the observed theme, accent, mode and app version
    beside every shot, so "which build is this frame from?" is a file read rather than
    the forensics that made this pass expensive.

Verification

  • cd website && npm install && npm run build → exits 0, 52 pages.
  • Leak check (CI has no secret scan — this is manual): exactly 46 127.0.0.1
    hits across website/src/content/docs/** + README.md, matching the recounted
    baseline. No private hostname, LAN IP, container id or box path in the diff.
  • Rig identity proven from /api/instance-config (dataDir + driveMode: batch),
    never from pm status or /api/health — both are satisfied by a stale squatter.
  • Stale-build guard: the rig was rebuilt at main and /config confirmed to
    render the Appearance section, which cannot exist before 75c3935. Without this
    every "re-shot" frame would be the old UI again, and nothing else would show it.
  • md5sum on every shot — no byte-identical pairs. Two unframed shots of one URL
    at one viewport land as the same file.
  • Every committed frame was opened and looked at. strings shot.png is not a leak
    check — rendered text is pixels.

Docs-only: no changeset, no version bump.

Not in this PR

HomeLab Agent added 5 commits August 10, 2026 13:49
root-home, adopt-row, adopt-modal, project-settings and the trigger tool
picker were all shot before the design work landed (#763/#768/#769/#780/#801).
Every one showed chrome, surfaces, type and accent the redesign replaced, on
pages whose prose is in the present tense.

Shot from the docs-media rig at v0.69.0, Foundation/dark, hue null, tint 0 —
the out-of-the-box appearance.  capture.mjs now pins the theme via
addInitScript (the keys are read by a pre-paint script, so a post-goto
evaluate catches a mid-swap frame) and asserts it applied.
… panel + theme quartet

Foundation dark at the out-of-box default (theme quartet excepted). Shot against
a rebuilt rig at main, verified post-redesign via the Appearance section.

Discover is NOT included: the rig has no discoverable Claude Code history, so
the route renders its empty state. Needs seeding.
The previous frame caught the list when tidepool held a single chat, so it
showed the adopted row alone — no contrast, which is the entire point of the
image on using/working-in-chats.md:80. Re-shot with four ordinary chats above
it, so the terminal provenance marker reads AS a distinction.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 10, 2026

Copy link
Copy Markdown

Deploying paddock with  Cloudflare Pages  Cloudflare Pages

Latest commit: f60fe9d
Status: ✅  Deploy successful!
Preview URL: https://e58fed9d.paddock-7u2.pages.dev
Branch Preview URL: https://docs-media-v069.paddock-7u2.pages.dev

View logs

HomeLab Agent added 2 commits August 10, 2026 14:06
…shot

Four runtime themes plus a free accent picker make "which theme is this?"
unanswerable from a PNG — which is most of what made this re-shoot expensive to
reason about. capture.mjs now writes <shot>.png.json observed FROM THE LIVE PAGE
(theme, light/dark, hue, tint, solved accent, app version, route, viewport), so
a silently-failed theme is detectable after the fact rather than only at capture
time. shots.manifest.json is the committed aggregate.

Immediately useful: the quartet records four distinct accent triples, which is
what proves the theme applied rather than just the label being written.
The re-shot frame names a different chat and shows four ordinary rows above
the adopted one; the alt text still described the previous capture.
edspencer pushed a commit that referenced this pull request Aug 10, 2026
git rm --cached was not enough: it left the files in the working tree, where the
next blanket `git add -A` re-staged them and silently undid the split. Removing
them from the tree on this branch is what actually holds.

They are committed on docs/media-v069 (#828), which is where #815 should take
them from — an asset and the reference to it belong in one PR, and whats-new.mdx
is on #815.
@edspencer

Copy link
Copy Markdown
Owner Author

Superseded by #829 (captures) and #830 (rig tooling + runbook), which split this into two independently reviewable changes and are a strict superset of it. Closing to keep the review queue honest — no content is lost.

@edspencer edspencer closed this Aug 10, 2026
edspencer pushed a commit that referenced this pull request Aug 10, 2026
edspencer added a commit that referenced this pull request Aug 11, 2026
…830)

* docs(docs-media): document the rig contract, sidecars and the leak-pattern rule

* chore(docs-media): commit the rig launcher, seed and fixtures

seed.mjs and serve.sh existed only on one box and were full of its paths, ports
and process-manager specifics, which made tools/docs-media/ unusable by anyone
else. Both are now driven by PADDOCK_RIG_{HOME,CLONE,PROJECTS,FIXTURES}.

serve.sh was RETYPED from a spec rather than copied: the original held a live
OAuth token in plaintext, and a copy keeps the credential in the editor buffer
and in shell history. It carries no credential and cannot: it re-execs under
env -i, and the rig runs a fake claude on the batch runtime.

capture.mjs gains theme pinning via addInitScript (the appearance keys are read
by a pre-paint script, so writing them after goto shoots mid-swap) and the
provenance sidecars.

* docs(website): re-shoot the stale stills on the 0.67+ UI

Every UI-bearing image on the site predated the redesign, including the seven
committed the previous pass — those were merged 41 minutes before the first
design commit, so age was never the criterion.

Foundation dark at the out-of-box default (DEFAULT_APPEARANCE; paddock:theme
defaults to dark), which is what a reader sees on first boot. The theme quartet
is the deliberate exception: there the choice itself is the subject.

* docs(runbook): correct the 127.0.0.1 baseline split (39 docs + 7 README = 46)

The previous paragraph attributed all 46 to the docs subtree and then added
"plus 6 in README.md", summing to 52. A baseline that overcounts is how a
genuine new hit hides inside an expected one.

* docs(runbook): what a DESIGN release changes about a docs pass

Four things this pass proved the runbook lacked, all in section 5:

- The stale-build trap gets its own warning. Serving dist/ from a checkout
  that predates the release means every re-shot frame is the old UI, and it
  fails SILENTLY: rig up, seed passes, leak scan passes, every shot succeeds,
  md5sum clean. Recorded as a positive check against the SERVED BUNDLE — name
  a UI element only the new build can paint, then go and look at it — rather
  than against the git state of a directory.

- Bucket by the TENSE of the surrounding prose, not the directory. A What's
  New entry records a release as it shipped, so an old-UI frame there is
  correct and re-shooting it falsifies the archive; a current-behaviour page
  showing obsolete UI is simply wrong. Directory is a proxy that fails on
  exactly the dual-use assets cited from both — fork those.

- The rig's own fragility. A projects root on a non-persisted volume loses the
  whole tree on a restart while the data dir survives, so the instance boots to
  zero projects plus orphaned job records: it does not fail, it lies. Wipe both
  or neither, and commit the launcher and seed so the rig is reproducible.

- Appearance is no longer determined by the URL. Pin the theme with
  addInitScript (the keys are read pre-paint), clear the solved-accent cache,
  assert it applied, and never verify a theme by grepping CSS.

Plus the seeding race (rename in a second pass or the transcript's own title
resolution clobbers it) and the md5sum/framing discipline for near-identical
shots. Box paths, ports and domains are written as placeholders.

* docs(runbook): pinning the appearance, and seeding a rig with texture

Runtime themes mean a screenshot is no longer determined by the URL. The keys
are read by a pre-paint inline script, so a capture must write them with
addInitScript rather than page.evaluate after goto (which yields a mid-swap
frame), must clear the solved-accent cache (keyed theme:mode — a stale entry
paints the previous theme's accent pre-boot), and should assert it applied.
Never verify a theme by grepping CSS: OKLCH serialises as oklch(...) and the
accent token is a bare RGB triple, so a regex reader scores a themed build zero.

Also the seeding race: a chat is the product of a turn, so an API-only seed
photographs as an empty app; and renaming a chat immediately after the turn
completes loses to the transcript's own title resolution.

* docs(website): remove the whats-new stills from the captures PR

git rm --cached was not enough: it left the files in the working tree, where the
next blanket `git add -A` re-staged them and silently undid the split. Removing
them from the tree on this branch is what actually holds.

They are committed on docs/media-v069 (#828), which is where #815 should take
them from — an asset and the reference to it belong in one PR, and whats-new.mdx
is on #815.

* chore: drop the captures from this PR — they ship in #829

The stills were pushed onto this branch by a concurrent worker. This PR is the
rig and the tooling, which is code-shaped and independently reviewable; the
frames it produces are a separate review with separate criteria.

* docs(runbook): negative findings must be re-verified by the relaying party

The most valuable thing this pass produced. A wrong positive claim is caught by
the next reader; a wrong negative is acted on by DELETING the evidence — correct
prose is removed and it reads as diligence.

Happened twice within an hour in opposite directions over one file: the accent
picker's floor was reported unenforced (wrong — repairFill does repair derived
tokens at runtime), then over-corrected into "nothing is enforced" (also wrong),
neither time by anyone who opened accent.ts. The rule therefore binds the
corrector too, and negatives must be scoped to their narrowest true form.

Records the control that actually worked: the issue-filing child was told to
re-verify against main before filing, did, and its issues carry the narrow true
claim while the broken version was still circulating above it.

* docs: drop the runbook from this PR — #833 owns it

The same 216-line diff is in #833. Two open PRs editing one 760-line file
conflict on whichever merges second, and this PR is tooling: tools/docs-media/**
is independently reviewable and independently revertable without it.

* chore(docs-media): drop the manifest entry for a shot that ships nowhere

docs-discover.png was captured and deliberately not committed: the rig has no
discoverable Claude Code history, so the route renders its empty state. A
manifest entry for a frame that exists in no PR invites someone to go looking
for it.

The twelve remaining entries all ship — seven in #829 and five in #815. Those
five are NOT pruned despite having moved out of #829: they still ship, and the
manifest is the only record of which theme and solved accent each was shot at,
which is exactly what a reviewer of #815 needs.

* docs(docs-media): restore the README the rig PR is supposed to carry

Building this branch with 'git checkout <other-branch> -- tools/docs-media'
took the README from that branch too, silently reverting the rig-contract and
sidecar sections to main's 69-line version — so the PR shipped the scripts with
none of the documentation that makes them usable, which was half the point.

Also adds the staleness note: because each sidecar records the app version,
'is this frame stale after a repaint?' is a file read rather than an inspection
of every PNG.

* docs(docs-media): note the uniform-timestamp tell as a known seed improvement

seed.mjs creates its chats in one run, so every row in a capture carries the
same relative age — the v0.69 Home frame reads '2h ago' nine times. That is the
hand-seeded-fixture tell the runbook already warns about; a real instance has
this morning's work beside last week's.

The adoptable sessions already back-date their mtimes; the chats do not.
Recorded as a known improvement, not a fix: no committed frame is wrong because
of it.

* chore(docs-media): make HOME overridable, and actually forward it

On Discover the home path IS the content: DiscoverView renders {candidate.path}
and {result.homeDir} verbatim into <code>. So cropping fails (the path is the
subject), masking fails (the leak-masker would blank the element being shot),
and a symlink fails (paddock canonicalises for display). A rig whose HOME sits
under a scratch directory cannot produce a publishable Discover frame at all —
the shot is lost at launch, not at capture.

The override was inert as first written: env -i wipes the environment before the
re-exec, and PADDOCK_RIG_USER_HOME was not in the forwarding list, so the second
pass always fell back to the default. Same for PADDOCK_RIG_FIXTURES, which the
README documented and the launcher could never receive. Both now forwarded, and
verified with a control: without the override HOME resolves to <rig>/home, with
it to the path given.

Also documents the no-recorded-cwd exclusion — a staged transcript without a cwd
is dropped silently, which is the most common way a Discover seed fails while
looking like it worked.

* fix(docs-media): make HOME overridable, or Discover can never be shot

serve.sh hard-coded HOME to a scratch directory. On the Discover screen the home
path IS the content: DiscoverView renders {candidate.path} and {result.homeDir}
verbatim in <code>, with no truncation. So that rig cannot produce a publishable
Discover frame, and every escape is blocked — cropping loses the subject, the
leak-masker would blank the subject, and a symlink is defeated by paddock
canonicalising for display. The reason is in the comment because the reason is
what stops someone simplifying it back.

Also implements PADDOCK_RIG_FIXTURES, which the README documented and serve.sh
never had — so the committed fixtures.json was a dead file and the authored
replies never reached the fake claude. Both new vars are threaded through the
env -i re-exec; without that the override is scrubbed and silently does nothing.

Documents the other Discover trap: a staged transcript with no cwd is excluded
as no-recorded-cwd, which fails while looking like it worked.

---------

Co-authored-by: HomeLab Agent <homelab-infra@valfenda.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant